#include <iostream>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <chrono>
#include <vector>

static constexpr int g_repository_size{10};
static constexpr int g_total{ 50 };

std::mutex g_mutex;
std::mutex g_counter_mutex;
std::mutex g_consumer_counter_mutex;
std::condition_variable g_repository_not_full;
std::condition_variable g_repository_not_empty;

int g_item_buffer[g_repository_size]{0};

static int g_read_position{0};
static int g_write_postion{0};
static int g_item_counter{ 0 };
static int g_consumer_item_counter{ 0 };

void produce_item(int i)
{
	std::unique_lock<std::mutex>_lck(g_mutex);

	//item buffer is full,just wait here.
	while (((g_write_postion + 1) % g_repository_size) == g_read_position)
	{
		std::cout << "produce is waiting for an empty slot..." << std::endl;	//the repository is full
		g_repository_not_full.wait(_lck);
	}
	g_item_buffer[g_write_postion] = i;
	g_write_postion++;
	if (g_write_postion == g_repository_size)
		g_write_postion = 0;
	g_repository_not_empty.notify_all();
}

int consume_item()
{
	int data{};
	std::unique_lock<std::mutex>_lck(g_mutex);
	//item buffer is empty,just wait here.
	while (g_write_postion == g_read_position)
	{
		std::cout << "consumer is waiting for item ..." << std::endl;			//the repository is empty
		g_repository_not_empty.wait(_lck);
	}
	data = g_item_buffer[g_read_position];
	g_read_position++;
	if (g_read_position >= g_repository_size)
		g_read_position = 0;
	g_repository_not_full.notify_one();
	return data;
}

void produce_thread()
{
	bool _read_exit{ false };
	while (true)
	{
		std::unique_lock<std::mutex>_lck(g_counter_mutex);
		if (g_item_counter < g_total)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(600));
			++g_item_counter;
			produce_item(g_item_counter);
			std::cout << "producer thread_id: " << std::this_thread::get_id() << "produce " << g_item_counter << " products" << std::endl;
		}
		else
			_read_exit = true;
		if (_read_exit)
			break;
	}
	std::cout << "producer thread id: " << std::this_thread::get_id() << " is existing..." << std::endl;
}

void consume_thread()
{
#if 1
	bool read_exit{ false };
	while (true)
	{
		std::this_thread::sleep_for(std::chrono::milliseconds(600));
		std::unique_lock<std::mutex>_lck(g_consumer_counter_mutex);
		if (g_consumer_item_counter < g_total)
		{
			auto _item{ consume_item() };
			++g_consumer_item_counter;
			std::cout << "consumer thread_id :" << std::this_thread::get_id() << "consume" << _item << " product" << std::endl;
		}
		else
			read_exit = true;
		if (read_exit)
			break;
	}
	std::cout << "consumer thread_id: " << std::this_thread::get_id() << " is exiting..." << std::endl;
#else
	static int _cnt{ 0 };
	while (true)
	{
		std::this_thread::sleep_for(std::chrono::milliseconds(500));
		auto _item{ consume_item() };
		std::cout << "consumer consume :" << _item << " product" << std::endl;
		if (g_total == ++_item)
			break;
	}
#endif // 0


}

int main(int argc, char** argv)
{
	std::vector<std::thread>_consumer_thread_container{};
	for (int i = 0; i < 5; ++i)
		_consumer_thread_container.push_back(std::thread(consume_thread));


	std::vector<std::thread>_thread_container{};
	for (int i = 0; i < 5; ++i)
		_thread_container.push_back(std::thread(produce_thread));

	for (auto& i : _thread_container)
		i.join();

	for (auto& i : _consumer_thread_container)
		i.join();
}